#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <omp.h>
#include "mpi.h"
#include "tree.h"

#define MAXIN 64
#define FILEIN "palavrasfiltrado.txt"
#define FILEOUT "resultado.txt"

#define NTHREADS 5
#define MAXPROC 16

//gerador de palavras
void randword(char * str, int size){
	int i;

	for(i = 0; i < size; i++)
		str[i] = 'a' + rand()%26;

	str[size] = '\0';
}


//as palavras menores possuem uma lista encadeada doas palavras maiores que compoe
void updatelarger(multtree * mtree, index_t i){
	int len;
	index_t it, larger;

	len = mtree->node[i].len;

	for(it = mtree->node[i].lpart; it != ENDIDX; it = mtree->list[it].next){
		larger = mtree->list[it].word;
		mtree->node[larger].len -= len;	//subtrai o tamanho da parte do tamanho da palavra grande
		if(mtree->node[larger].len == 0) mtree->node[larger].flag |=  _FGEN;	 //Encontrou todas as partes seta palavra como gerada
	}
}

int main (int argc, char **argv){
	int i, j, k, rank, nproc;
	int nwords;
	unsigned long t1, t2;

	multtree * mtree;
	FILE * pfile;
	char input[MAXIN];

	MPI_Status Stat;
	MPI_Request req_rec[MAXPROC];
	MPI_Request req_sen[MAXPROC];
	int smsg;
	int rmsg[MAXPROC];
	int rbuff[MAXPROC];
	int sbuff[MAXPROC];


	MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);


	//aloca as arvores
	mtree = create_multtree();
	if(mtree == NULL) return 1;



	if (rank == 0){  //processo MPI 0 preenche a arvore e compartilha com os outros
		t1 = clock();
		pfile = fopen (FILEIN , "r");
		if (pfile == NULL){
			perror ("Error opening file\n");
			return 1;
		}
		while( fgets(input, MAXIN, pfile) != NULL ){
			for(i = 0; i < MAXIN && input[i] != '\0' && input[i] != '\n'; i++);

			input[i] = '\0';

			//insere a palavra
			if((i = insertword(mtree, input)) < -1){
				printf("Erro insere [%d]\n", i);
				break;
			}
		}

		//quebra as maiores do que 5 letras em menores
		dividelarger(mtree);
		fclose(pfile);

		MPI_Comm_size(MPI_COMM_WORLD, &nproc);
		nproc--;

		for(i = 0; i < nproc; i++){

			// Envia as arvores para os outros processos

			MPI_Send( &(mtree->nextfree), 1 , MPI_INT, i+1, 1, MPI_COMM_WORLD);
			MPI_Send( mtree->nodescount, 5 , MPI_INT, i+1, 2, MPI_COMM_WORLD);
			MPI_Send( mtree->root, 5 , MPI_INT, i+1, 3, MPI_COMM_WORLD);
			MPI_Send( &(mtree->strcur), 1 , MPI_INT, i+1, 4, MPI_COMM_WORLD);
			MPI_Send( &(mtree->nextlistn), 1 , MPI_INT, i+1, 5, MPI_COMM_WORLD);
			MPI_Send(mtree->node , sizeof(avlnode)*(mtree->nextfree) , MPI_CHAR, i+1, 6, MPI_COMM_WORLD);
			MPI_Send(mtree->str , mtree->strcur , MPI_CHAR, i+1, 7, MPI_COMM_WORLD);
			MPI_Send(mtree->list , sizeof(listnode)*(mtree->nextlistn) , MPI_CHAR, i+1, 8, MPI_COMM_WORLD);
		}
	}	
	else {

		// Recebe as arvores

		MPI_Recv(&(mtree->nextfree), 1 , MPI_INT, 0, 1, MPI_COMM_WORLD, &Stat);
		MPI_Recv( mtree->nodescount, 5 , MPI_INT, 0, 2, MPI_COMM_WORLD, &Stat);
		MPI_Recv( mtree->root, 5 , MPI_INT, 0, 3, MPI_COMM_WORLD, &Stat);
		MPI_Recv( &(mtree->strcur), 1 , MPI_INT, 0, 4, MPI_COMM_WORLD, &Stat);
		MPI_Recv( &(mtree->nextlistn), 1 , MPI_INT, 0, 5, MPI_COMM_WORLD, &Stat);
		MPI_Recv(mtree->node , sizeof(avlnode)*(mtree->nextfree) , MPI_CHAR, 0, 6, MPI_COMM_WORLD, &Stat);
		MPI_Recv(mtree->str , mtree->strcur , MPI_CHAR, 0, 7, MPI_COMM_WORLD, &Stat);
		MPI_Recv(mtree->list , sizeof(listnode)*(mtree->nextlistn) , MPI_CHAR, 0, 8, MPI_COMM_WORLD, &Stat);

	}



	//Mestre
	if(rank==0){
	pfile = fopen (FILEOUT , "w");

	nwords = mtree->nextfree;

	MPI_Request req_rec[MAXPROC];
	MPI_Request req_sen[MAXPROC];
	
	smsg = 9;

	for(i = 0; i < nproc; i++){
		MPI_Irecv(rbuff + i, 1,  MPI_INT, i+1, 9, MPI_COMM_WORLD, req_rec+i);
		rmsg[i] = 10;
	}

	printf("%d 5 %d ", rank, nwords);

	while(nwords > 0){
	
		//verifica se recebeu alguma palavra de outro processo
		for(i = 0; i < nproc; i++){
			MPI_Test(req_rec+i, &k, &Stat);
			if(k != 0){
				// se a palavra na tinha sido achada antes
				if(mtree->node[rbuff[i]].found == 0){
					mtree->node[rbuff[i]].found = 1;

					//se eh uma palavra nao apenas parte e outra
					if(mtree->node[rbuff[i]].flag & _FWORD)
							fprintf(pfile, "%s\n", mtree->str + mtree->node[rbuff[i]].str);


					nwords--; //decrementa palavras restante

					if(!(nwords%1000)){
						t2 = clock();
						printf("%lf ", ((double) (t2 - t1))/CLOCKS_PER_SEC);
					}
				}
				//Prepara para receber a proxia palavra enviada pelo no
				MPI_Irecv(rbuff + i, 1,  MPI_INT, i+1, rmsg[i], MPI_COMM_WORLD, req_rec+i);
				rmsg[i]++;
			}
		}
	}


	printf("\n");

	fclose(pfile);
	}

	//escravos
	else {
	omp_set_num_threads(NTHREADS);

	#pragma omp parallel private(sbuff, rbuff, smsg, rmsg, req_sen, req_rec)
	{
		int i, j, n, cmp, total, smsg;
		index_t idx;
		char s[6];
	
		j = omp_get_thread_num();
		srand(time(NULL) + j + rank*5);

		if(j < 4){
			//Para palavras de tamanho 2 3 4 ou 5

			while(mtree->nodescount[j] > 0){
				randword(s, j+2); //gera randomicamente
				cmp = findnode(mtree, j+2, s, &idx); //busca na respectiva arvore
				if(cmp==0 && mtree->node[idx].found == 0){
					mtree->node[idx].flag |= _FGEN; //marca como gerada
				}
			}
		}
		else{
			//esse ultimo faz a comunicacao por MPI trata as palavras maiores do que 5 letras
			total = n = mtree->nextfree;

			smsg = 9;


			while(total > 0){
				//percorre o vetor que possue os nos das arvore
				for(i = 0; i < n; i++)

					//verifica se o no "acabou" de ser gerado
					if((mtree->node[i].found == 0) && (mtree->node[i].flag & _FGEN)){

						//envia o indice dele para o proceso mestre
						sbuff[0] = i;
						MPI_Isend(sbuff, 1,  MPI_INT, 0, smsg, MPI_COMM_WORLD, req_sen);
						smsg++;

						//se a palavra faz compoe uma ou mais palavras maiores, contabiliza nas maiores
						if(mtree->node[i].flag & _FPART)
							updatelarger(mtree, i);
						
						//verifica a qual arvore ela pertence
						if(mtree->node[i].flag & _LARGER) j = 4;
						else j = mtree->node[i].len - 2;

						//decrementa os contadores
						mtree->nodescount[j]--;
						total--;			

						//marca como encontrada
						mtree->node[i].found = 1;
					}
					
			}
			
		}
	}
	}

	delete_multtree(mtree);

	MPI_Finalize();
	return 0;
}
